/** @file    Settings.cpp
 *  @author  Alessandro Polo
 *  @version $Id: Settings.cpp 2833 2010-08-02 23:12:57Z alex $
 *  @brief
 * File containing methods for the wosh::Settings class.
 * The header for this class can be found in Settings.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <core/Settings.h>
 #include <core/ObjectAllocator.h>
 #include <core/Utilities.h>
 #include <core/FileSystem.h>
 #include <fstream>
 #include <iostream>
 #include <algorithm> // folder parsing (sorting)


using namespace std;
namespace wosh {

 WOSH_REGISTER(wosh::Settings, "wosh::Object", 1.01, _static_Settings)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

Settings::Settings()
	: Object() {
	Object::setName(_Settings_NAME);
	Log.setContext(_Settings_NAME);
	Log.setIndent( 2 );
	Log.setLevel( LOG_INFO );
 }

Settings::Settings( int argc, char **argv )
	: Object() {
	setName(_Settings_NAME);
	Log.setContext(_Settings_NAME);
	Log.setIndent( 2 );
	Log.setLevel( LOG_INFO );
	Log(LOG_VERBOSE, "Initializing from Args.." );
	loadFromArgs(argc, argv);
 }

Settings::~Settings() {
	Log(LOG_DEBUG, ":~Settings() : Destroying.." );
	this->groups.clear();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Settings::loadFromArgs( int argc, char **argv, bool overwrite ) {
	if ( argc == 0 || argv == NULL ) {
		Log(LOG_VERBOSE, ":loadFromArgs() : No Arguments.." );
		return WRET_ERR_PARAM;
	 }
	long prevGroups = countGroups();
	long prevKeys = countKeys();
	string programName = string( argv[0] );
	Log(LOG_DEBUG, ":loadFromArgs() : "_Settings_KEY_ProgramName" = %s", programName.c_str() );
	if ( overwrite ) {
		set( _Settings_KEY_ProgramName, programName, "" );
	 }
	string buffer;
	for( int i=1; i<argc; ++i ) {
		buffer = string( argv[i] );
		if ( buffer.size() < 3 ) {
			//invalid format
			continue;
		 }
		if ( buffer.at(0) == '-' && buffer.at(1) == '-' ) {
			injectLine( buffer.substr(2), "", -1, true );
		 }
	 }
	long currGroups = countGroups();
	long currKeys = countKeys();
	Log(LOG_INFO, ":loadFromArgs() : +%d groups[%d], +%d keys[%d]", currGroups-prevGroups, currGroups, currKeys-prevKeys, currKeys );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Settings::loadConfigurationFile( const std::string& filepath ) {
	FilePath fDir;
	if ( !fDir.set(filepath) ) {
		Log(LOG_CRITICAL, ":loadConfigurationFile(%s) Invalid filename!", filepath.c_str() );
		return WRET_ERR_PARAM;
	 }
	string localFile = fDir.getPathLocal();
	ifstream infile;
	infile.open( localFile.c_str(), ifstream::in );
	if ( !infile.is_open() ) {
		Log(LOG_CRITICAL, ":loadConfigurationFile('%s') : Invalid filename (can't open)", localFile.c_str() );
		return WRET_ERR_PARAM;
	 }
	long prevGroups = countGroups();
	long prevKeys = countKeys();
 	string buffer;
	string cGroup = "";
	size_t cPos;
	long iLine = 0;
	Log(LOG_VERBOSE, ":loadConfigurationFile('%s') : Parsing file..", localFile.c_str() );
	while ( infile.good() ) {
		if ( infile.eof() )	break;
		getline( infile, buffer);
		++iLine;
		// ignore comments
		cPos = buffer.find('#');
		if ( cPos != string::npos )
			buffer.erase( cPos );
		// remove endline
		cPos = buffer.find('\r');
		if ( cPos != string::npos )
			buffer.erase( cPos );
		cPos = buffer.find('\n');
		if ( cPos != string::npos )
			buffer.erase( cPos );
		// ignore empty lines or comment-cleaned
		if ( buffer.empty() )
			continue;
		if ( buffer.at(0) == '[' && buffer.at(buffer.size()-1) == ']' ) {
			cGroup = buffer.substr(1, buffer.size()-2);
			Log(LOG_VERBOSE, ":loadConfigurationFile() : Entering Group '%s' at line %ld", cGroup.c_str(), iLine );
			if ( cGroup.size() > 1 ) { // optional block, here cos we want empty groups 
				this->groups.transactionBeginWrite();
				if ( !this->groups.exists(cGroup) )
					this->groups.set( cGroup, new PropertiesProvider() );
				this->groups.transactionEnd();
			 }
			continue;
		 }
		injectLine( buffer, cGroup, iLine, false );
	 }
	infile.close();
	long currGroups = countGroups();
	long currKeys = countKeys();
	Log(LOG_INFO, ":loadConfigurationFile(%s) : +%d groups[%d], +%d keys[%d]", localFile.c_str(), currGroups-prevGroups, currGroups, currKeys-prevKeys, currKeys );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
										// generic path (wosh or local)
WRESULT Settings::loadConfigurationFolder( const std::string& folder, const std::string& pattern ) {
	WRESULT ret = WRET_OK;
	vector<string> files;
	ret = FileSystem::getFilesOf( folder, files, false );
	if ( WFAILED(ret) ) {
		Log(LOG_CRITICAL, ":loadConfigurationFolder() : FAILED getting files of %s", folder.c_str() );
		return ret;
	 }
	if ( files.empty() ) {
		Log(LOG_WARNING, ":loadConfigurationFolder() : No files found in %s", folder.c_str() );
		return WRET_ERR_PARAM;
	 }
	Log(LOG_VERBOSE, ":loadConfigurationFolder() : %d files found in %s", files.size(), folder.c_str() );
	std::sort( files.begin(), files.end() );
	std::vector<string>::const_iterator it;
	for ( it=files.begin(); it!=files.end(); ++it ) {
		if ( (*it).find(pattern) == string::npos )
			continue;
		ret += loadConfigurationFile( FilePath::combine(folder, *it) );
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Settings::injectLine( const std::string& dataLine, const std::string& groupTip, long iLine, bool never_overwrite ) { // XXX/YYY=ZZZ
	std::string group = groupTip;
	std::string key = "";
	Variant value;
	Log(LOG_DEBUG, ":injectLine('%s') : Parsing line %ld", dataLine.c_str(), iLine );
	WRESULT ret = parseLine( dataLine, group, key, value );
	if ( WFAILED(ret) ) {
		Log(LOG_WARNING, ":injectLine() FAILED#%d : Parsing Line %ld!", ret, iLine );
		return ret;
	 }
	return set( key, value, group, never_overwrite );
 }

WRESULT Settings::parseLine( const std::string& dataLine, std::string& group, std::string& key, Variant& value ) { // XXX/YYY=ZZZ
	size_t eqPos = dataLine.find('=');
	if ( eqPos == string::npos ) {
		Log(LOG_CRITICAL, ":parseLine() : Char '=' not found!" );
		return WRET_ERR_PARAM;
	 }
	key = dataLine.substr(0,eqPos);
	if ( key.empty() ) {
		Log(LOG_CRITICAL, ":parseLine() : Key field is empty!" );
		return WRET_ERR_PARAM;
	 }
	Utilities::trimSpaces( key );
	size_t slPos = key.find('/');
	if ( slPos != string::npos ) {
		if ( group != "" )
			Log(LOG_WARNING, ":parseLine() : Nested group/key in group '%s' : key '%s'", group.c_str(), key.c_str() );
		group = key.substr(0,slPos);
		key = key.substr(slPos+1);
		Utilities::trimSpaces( group );
		Utilities::trimSpaces( key );
	 }
	if ( eqPos+1 >= dataLine.size() ) {
		Log(LOG_WARNING, ":parseLine() : Value field is empty!" );
		value.clear();
	 }
	else
		value.fromString( dataLine.substr(eqPos+1) );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Settings::set( const std::string& key, const Variant& value, const std::string& group, bool never_overwrite ) {
	if ( key.empty() ) {
		Log(LOG_WARNING, ":set() : Empty Key!" );
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( group == "" || group == "Generic" ) {
		if ( this->genericGroup.exists(key) ) {
			if ( never_overwrite ) {
				Log(LOG_WARNING, ":set(Generic/%s) : Key exists, Ignoring due 'never_overwrite' flag", key.c_str() );
				return WRET_OK;
			 }
			Log(LOG_VERBOSE, ":set(Generic/%s)* Overwriting value..", key.c_str() );
		 }
		else
			Log(LOG_VERBOSE, ":set(Generic/%s) Adding Property..", key.c_str() );
		return this->genericGroup.set(key, value);
	 }
	PropertiesProvider* cPP = NULL;
	MutexLockerWrite mL(this->groups.getMutex());
	if ( this->groups.exists(group) )
		cPP = this->groups.find(group);
	else {
		cPP = new PropertiesProvider();
		this->groups.set(group, cPP);
	}
	if ( cPP == NULL ) {
		Log(LOG_CRITICAL, ":set(%s/%s) FAILED: Unable to access/create group!", group.c_str(), key.c_str() );
		return WRET_ERR_INTERNAL;
	 }
	if ( cPP->exists(key) ) {
		if ( never_overwrite ) {
			Log(LOG_WARNING, ":set(%s/%s) : Key exists, Ignoring due 'never_overwrite' flag", group.c_str(), key.c_str() );
			return WRET_OK;
		 }
		Log(LOG_VERBOSE, ":set(%s/%s) Overwriting value..", group.c_str(), key.c_str() );
	 }
	else
		Log(LOG_VERBOSE, ":set(%s/%s) Adding Property..", group.c_str(), key.c_str() );
	cPP->set(key, value);
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// STATIC MEMBERS

PropertiesProvider* Settings::getSettingsOf( const std::string& group ) {
	MutexLockerRead mL(this->groups.getMutex());
	return getSettingsOf_(group);
 }

PropertiesProvider* Settings::getSettingsOf_( const std::string& group ) {
	if ( group == "" || group == "Generic" ) return &this->genericGroup;
	return this->groups.find(group);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Variant Settings::getValueOf( const std::string& group, const std::string& key, const Variant& default_value ) {
	MutexLockerRead mL(this->groups.getMutex());
	PropertiesProvider* group_p = getSettingsOf_(group);
	if ( group_p != NULL && group_p->exists(key) ) 
		return group_p->getValueOf(key, default_value);
	return Variant(default_value);
 }

long Settings::countKeys() const {
	long count = this->genericGroup.size();
	MutexLockerRead mL(this->groups.getMutex());
	tStrPropertiesProviderMap::Iterator it;
	const tStrPropertiesProviderMap::ConstIterator it_end = this->groups.end();
	for ( it=this->groups.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		count += it->second->size();
	 }
	return count;
 }

long Settings::countKeysOf(const std::string& group) const {
	if ( group == "Generic" ) return this->genericGroup.size();
	MutexLockerRead mL(this->groups.getMutex());
	if ( this->groups.find(group) == NULL )
		return 0;
	return this->groups.find(group)->size();
 }

bool Settings::existsGroup( const std::string& group ) const {
	if ( group.size() == 0 ) return false;
	if ( group == "Generic" ) return true;
	if ( this->groups.exists(group) )
		return true;
	return false;
 }

int Settings::getGroups(std::vector<std::string>& groups_name) const {
	this->groups.getKeys(groups_name);
	return groups_name.size();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Settings& Settings::operator=(const Settings& m){
	if (this == &m) return *this; // same object?
	this->genericGroup = m.genericGroup;
	this->groups  = m.groups;
	return *this;
 }

bool Settings::operator==(const Settings &other) const {
	if ( this->genericGroup != other.genericGroup )
		return false;
	if ( this->groups != other.groups )
		return false;
	return true;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::string Settings::toString() const {
	return Utilities::format("%s[%d]", getClassNameOnlyOf(getClassName()), this->groups.size() );
 }

bool Settings::updateHash( DataHashContext* hash_context ) const {
	if ( hash_context == NULL ) return false;
	bool ret = true;
	ret = this->genericGroup.updateHash(hash_context) & ret;
	tStrPropertiesProviderMap::Iterator it;
	const tStrPropertiesProviderMap::ConstIterator it_end = this->groups.end();
	for ( it=this->groups.begin(); it!=it_end; ++it ) {
		ret = hash_context->update( it->first ) & ret;
		if ( it->second == NULL ) continue;
		ret = it->second->updateHash(hash_context) & ret;
	 }
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
